﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Linq;
using Common.DataTypes;


namespace LINQAmazonProvider
{
    public class AmazonExpressionVisitor 
    {
        private Dictionary<string, string> _searchParameters;

        public Dictionary<string,string> ProcessExpression(Expression expression)
        {
            _searchParameters = new Dictionary<string,string>();
            VisitExpression(expression);
            return _searchParameters;
        }

        protected virtual void VisitExpression(Expression expression)
        {
            if (expression.NodeType == ExpressionType.AndAlso)
            {
                VisitAndAlso((BinaryExpression)expression);
            }
            else if (expression.NodeType == ExpressionType.Equal)
            {
                VisitEqual((BinaryExpression)expression);
            }
            else if (expression.NodeType == ExpressionType.LessThanOrEqual)
            {
                VisitLessThanOrEqual((BinaryExpression)expression);
            }
            else if (expression is MethodCallExpression)
            {
                VisitMethodCall((MethodCallExpression)expression);
            }
            else if (expression is LambdaExpression)
            {
                VisitExpression(((LambdaExpression)expression).Body);
            }
        }

        private void VisitAndAlso(BinaryExpression andAlso)
        {
            VisitExpression(andAlso.Left);
            VisitExpression(andAlso.Right);
        }

        private void VisitEqual(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess) 
            {
                parameterName = ((MemberExpression)expression.Left).Member.Name;

                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = (string)((ConstantExpression)expression.Right).Value;
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, (string)GetMemberValue((MemberExpression)expression.Right));
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }

        }

        private void VisitLessThanOrEqual(BinaryExpression expression)
        {
            
        }

        private void VisitMethodCall(MethodCallExpression expression)
        {

            if ((expression.Method.DeclaringType == typeof(Queryable)) &&
              (expression.Method.Name == "Where"))
            {
                VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
            }
            else if ((expression.Method.DeclaringType == typeof(String)) &&
              (expression.Method.Name == "Contains"))
            {
                if (expression.Object.NodeType == ExpressionType.MemberAccess)
                {
                    MemberExpression memberExpr = (MemberExpression)expression.Object;
                    if (memberExpr.Expression.Type == typeof(Book))
                    {
                            Expression argument;

                            argument = expression.Arguments[0];
                            if (argument.NodeType == ExpressionType.Constant)
                            {
                                _searchParameters.Add(memberExpr.Member.Name, (String)((ConstantExpression)argument).Value);
                            }
                            else if (argument.NodeType == ExpressionType.MemberAccess)
                            {
                                _searchParameters.Add(memberExpr.Member.Name,(String)GetMemberValue((MemberExpression)argument));
                            }
                            else
                            {
                                throw new NotSupportedException("Expression type not supported: " + argument.NodeType.ToString());
                            }
                    }
                }
            }
            else
            {
                throw new NotSupportedException("Method not supported: " + expression.Method.Name);
            }
        }

        #region Helpers
        private Object GetMemberValue(MemberExpression memberExpression)
        {
            MemberInfo memberInfo;
            Object obj;

            if (memberExpression == null)
                throw new ArgumentNullException("memberExpression");

            // Get object
            if (memberExpression.Expression is ConstantExpression)
                obj = ((ConstantExpression)memberExpression.Expression).Value;
            else if (memberExpression.Expression is MemberExpression)
                obj = GetMemberValue((MemberExpression)memberExpression.Expression);
            else
                throw new NotSupportedException("Expression type not supported: " + memberExpression.Expression.GetType().FullName);

            // Get value
            memberInfo = memberExpression.Member;
            if (memberInfo is PropertyInfo)
            {
                PropertyInfo property = (PropertyInfo)memberInfo;
                return property.GetValue(obj, null);
            }
            else if (memberInfo is FieldInfo)
            {
                FieldInfo field = (FieldInfo)memberInfo;
                return field.GetValue(obj);
            }
            else
            {
                throw new NotSupportedException("MemberInfo type not supported: " + memberInfo.GetType().FullName);
            }
        }

        #endregion Helpers
    }
}
